using System;
using System.Collections.Generic;
using System.Linq;

namespace Tycho.Parser.Peg {
    public class SyntaxErrorLogger : ISyntaxErrorLogger {
        private List<SyntaxError> syntaxErrors;
        private int GreatestIndex;
        private List<ProductionContext> CurrentProductions;
        private List<ErrorRecovery> ErrorRecoveries;
        private bool InRecovery;
        public ParseResult ErrorResult { get; private set; }

        private class ProductionContext {
            public IProduction Production;
            public ParseContext Context;
            public IEnumerable<ProductionInvocation> ProductionInvocations;
            public IEnumerable<RecoveryPoint> RecoveryPoints;
        }

        private class ErrorRecovery {
            public IProduction Production;
            public int Index;
            public ParseResult Result;
        }

        public IEnumerable<SyntaxError> SyntaxErrors {
            get {
                return syntaxErrors;
            }
        }

        public bool HasLoggedErrors {
            get { return CurrentProductions != null; }
        }

        public SyntaxErrorLogger () {
            syntaxErrors = new List<SyntaxError> ();
            ErrorResult = new ParseResult (0, null, null);
            ErrorRecoveries = new List<ErrorRecovery> ();
        }

        public void LogSyntaxError (int index, IProduction production, ParseContext context) {
            LogSyntaxError (index, production, context, () => null);
        }

        public void LogSyntaxError (int index, IProduction production, ParseContext context, Func<IEnumerable<ProductionInvocation>> getProductionInvocations) {
            if (production.ErrorInformation != null && !InRecovery) {
                if (CurrentProductions == null) {
                    CurrentProductions = new List<ProductionContext> ();
                    CurrentProductions.Add (GetProductionContext (production, context, getProductionInvocations));
                    GreatestIndex = index;
                } else if (GreatestIndex < index) {
                    CurrentProductions.Clear ();
                    CurrentProductions.Add (GetProductionContext (production, context, getProductionInvocations));
                    GreatestIndex = index;
                } else if (GreatestIndex == index) {
                    CurrentProductions.Add (GetProductionContext (production, context, getProductionInvocations));
                }
            }
        }

        private static ProductionContext GetProductionContext (IProduction production, ParseContext context, Func<IEnumerable<ProductionInvocation>> getProductionInvocations) {
            return new ProductionContext {
                                             Production = production,
                                             Context = context,
                                             ProductionInvocations = getProductionInvocations (),
                                         };
        }

        public void CommitError (ITerm errorTerm) {
            if (CurrentProductions != null) {
                syntaxErrors.Add (new SyntaxError {Index = GreatestIndex, Term = errorTerm, ExpectedProductions = CurrentProductions.Select (pc => pc.Production.ErrorInformation)});
            }

            InRecovery = false;
            CurrentProductions = null;
        }

        public void SetErrorInMemoTable (char [] source) {
            foreach (var errorRecovery in ErrorRecoveries) {
                errorRecovery.Production.GetMemoTable (source)[errorRecovery.Index] = errorRecovery.Result;
            }

            if (CurrentProductions != null) {
                var productionContext = CurrentProductions.First ();
                productionContext.Production.GetMemoTable (source)[GreatestIndex] = ErrorResult;
            }
        }

        private string GetProductionName (IProduction p) {
//            if (p.Name != null) {
//                return p.Name;
//            } else {
                return p.GetHashCode ().ToString();
//            }
        }

        public void BeginRecovery () {
            InRecovery = true;
        }

        public void AddErrorRecovery (IProduction production, int index, ParseResult result) {
            ErrorRecoveries.Add (new ErrorRecovery {Production = production, Index = index, Result = result});
        }
    }
}